package com.java.framework.controller;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.java.framework.service.BaseService;
import com.java.framework.utils.Criteria;
import com.java.framework.utils.CriteriaUtils;
import com.java.framework.utils.http.RequestUtils;
import com.java.framework.utils.page.Pager;
import com.java.framework.utils.reflect.Reflections;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.beans.PropertyEditorSupport;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 控制器支持类
 * 
 * @author sunshine
 * @version 2017-3-23
 */
public abstract class BaseController<T, ID extends Serializable>{

	protected final static Logger log= LoggerFactory.getLogger(BaseController.class);
	
	private static String[] parsePatterns = { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", 
			"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm" };
	
	protected static final String SUCCESS = "success" ;
	protected static final String MESSAGE = "message" ;
	protected HttpServletRequest request;
    protected HttpServletResponse response;
    protected HttpSession session;
    // 具体的数据对象
 	protected T data;
 	private Class<T> clazz;
 	//数据集合
 	protected List<T> datas;

	/**
	 * 验证Bean实例对象
	 */
 	@Resource
	protected Validator validator;
	@Resource
	private TypeConverter converter;
	
	/**
	 * service
	 */
	@Autowired
	protected BaseService<T, ID> service ;

	public BaseController(){
		clazz = Reflections.getClassGenricType(getClass()) ;
	}

	@ModelAttribute
    protected void setReqAndRes(HttpServletRequest request, HttpServletResponse response){  
        this.request = request;
        this.response = response;
        this.session = request.getSession();
    }

	@ModelAttribute
	public T get(@RequestParam(required=false)ID id) {
		if(id instanceof String) {
			if(StringUtils.isNotBlank((String)id)){
				this.data = service.getOne(id);
			} else {
				initData();
			}
		} else {
			if (null != id){
				this.data = service.getOne(id);
			} else {
				initData();
			}
		}
		return this.data;
	}
	
	private void initData() {
		try {
			this.data = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	protected abstract String listPage();

	@RequestMapping(value = {"list", ""})
	public ModelAndView list(T data, HttpServletRequest request, HttpServletResponse response) {
		ModelAndView mv = new ModelAndView();
		
		Map<String, Object> searchParams = RequestUtils.getSearchParams(request, converter, clazz);
		Criteria<T> criteria = CriteriaUtils.buildCriteria(searchParams);
		
		Pager<T> pager = RequestUtils.getPager(request);
		
		//TODO:添加排序条件
		Sort sort = null ;
		pager.setSort(sort);
		
		Page<T> page = service.findAll(criteria, pager);

		pager.setPageList(page.getContent());
		pager.setTotalObjects(page.getTotalElements());
		
		mv.addObject("page", pager);
		mv.addAllObjects(searchParams);
		mv.setViewName(listPage());
		return mv;
	}

	protected abstract String formPage();

	@RequestMapping(value = "form")
	public String form(T data, Model model) {
		model.addAttribute("data", this.data);
		return formPage();
	}

	protected abstract String savePage();

	@RequestMapping(value = "save")
	public String save(T data, Model model, RedirectAttributes redirectAttributes) {
		if (!beanValidator(model, data)){
			return form(data, model);
		}
		service.save(data);
		addMessage(redirectAttributes, "保存成功");
		return savePage();
	}

	@RequestMapping(value = "delete")
	@ResponseBody
	public Map<String, Object> delete(@RequestParam ID id, RedirectAttributes redirectAttributes) {
		Map<String, Object> returnMap = Maps.newHashMap() ;
		try {
			this.service.delete(id);
			returnMap.put(SUCCESS, true);
			returnMap.put(MESSAGE, "删除成功") ;
		} catch (Exception e) {
			returnMap.put(SUCCESS, false);
			returnMap.put(MESSAGE, "删除失败") ;
			e.printStackTrace();
		}
		return returnMap;
	}

	protected HttpSession getSession() {
		return session;
	}

	protected HttpServletRequest getRequest() {
		return request;
	}

	protected HttpServletResponse getResponse() {
		return response;
	}
  
	/**
	 * 服务端参数有效性验证
	 * 
	 * @param object
	 *            验证的实体对象
	 * @param groups
	 *            验证组
	 * @return 验证成功：返回true；验证失败：将错误信息添加到 message 中
	 */
	protected boolean beanValidator(Model model, Object object, Class<?>... groups) {
		try {
			this.beanValidator(object, groups);
		} catch (ConstraintViolationException ex) {
			List<String> list = extractPropertyAndMessageAsList(ex.getConstraintViolations(), ": ");
			list.add(0, "数据验证失败：");
			addMessage(model, list.toArray(new String[list.size()]));
			return false;
		}
		return true;
	}

	/**
	 * 服务端参数有效性验证
	 * 
	 * @param object
	 *            验证的实体对象
	 * @param groups
	 *            验证组
	 * @return 验证成功：返回true；验证失败：将错误信息添加到 flash message 中
	 */
	protected boolean beanValidator(RedirectAttributes redirectAttributes,
			Object object, Class<?>... groups) {
		try {
			this.beanValidator(object, groups);
		} catch (ConstraintViolationException ex) {
			List<String> list = extractPropertyAndMessageAsList(ex.getConstraintViolations(), ": ");
			list.add(0, "数据验证失败：");
			addMessage(redirectAttributes, list.toArray(new String[list.size()]));
			return false;
		}
		return true;
	}
	
	private void beanValidator(Object object, Class<?>... groups){
		Set<? extends ConstraintViolation<?>> constraintViolations = validator.validate(object, groups);
		if (!constraintViolations.isEmpty()) {
			throw new ConstraintViolationException(constraintViolations);
		}
	}
	
	/**
	 * 辅助方法, 转换Set<ConstraintViolation>为List<propertyPath +separator+ message>.
	 */
	@SuppressWarnings("rawtypes")
	public static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations,
			String separator) {
		List<String> errorMessages = Lists.newArrayList();
		for (ConstraintViolation violation : constraintViolations) {
			errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
		}
		return errorMessages;
	}

	/**
	 * 添加Model消息
	 * 
	 * @param messages
	 *            消息
	 */
	protected void addMessage(Model model, String... messages) {
		StringBuilder sb = new StringBuilder();
		for (String message : messages) {
			sb.append(message).append(messages.length > 1 ? "<br/>" : "");
		}
		model.addAttribute(MESSAGE, sb.toString());
	}

	/**
	 * 添加Flash消息
	 * 
	 * @param messages
	 *            消息
	 */
	protected void addMessage(RedirectAttributes redirectAttributes,
			String... messages) {
		StringBuilder sb = new StringBuilder();
		for (String message : messages) {
			sb.append(message).append(messages.length > 1 ? "<br/>" : "");
		}
		redirectAttributes.addFlashAttribute(MESSAGE, sb.toString());
	}

	/**
	 * 初始化数据绑定 
	 * 1. 将所有传递进来的String进行HTML编码，防止XSS攻击 
	 * 2. 将字段中Date类型进行转换
	 */
	@InitBinder
	protected void initBinder(WebDataBinder binder) {
		// String类型转换，将所有传递进来的String进行HTML编码，防止XSS攻击
		binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				setValue(text == null ? null : StringEscapeUtils.escapeHtml4(text.trim()));
			}

			@Override
			public String getAsText() {
				Object value = getValue();
				return value != null ? value.toString() : "";
			}
		});
		// Date 类型转换
		binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
			@Override
			public void setAsText(String text) {
				try {
					if(StringUtils.isNotBlank(text)){
						setValue(DateUtils.parseDate(text,parsePatterns));
					}
				} catch (ParseException e) {
					e.printStackTrace();
				}
			}
		});
	}
	
}

